#include <stdio.h>
#include <string.h>
#include <sys/param.h>
#include <inttypes.h>

#include "esp_log.h"
#include "nvs_flash.h"
#include "esp_event.h"
#include "esp_netif.h"

#include "protocol_examples_common.h"
#include "protocol_examples_utils.h"
#include "esp_tls.h"

#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_system.h"

#include "esp_http_client.h"

#include "driver/gpio.h"
#include "cJSON.h"
#include "led_strip.h"

#define GPIO_INPUT_PIN  GPIO_NUM_9

#define MAX_HTTP_RECV_BUFFER    512
#define MAX_HTTP_OUTPUT_BUFFER  2048

// GPIO assignment
#define BLINK_GPIO  GPIO_NUM_8
// Numbers of tht LED in the strip
#define LED_STRIP_LED_NUMBERS 1
// 10MHz resolution, 1 tick = 0.1us (led strip needs a high resolution)
#define LED_STRIP_RMT_RES_HZ (10 * 1000 * 1000)

static const char *TAG = "HTTP_CLIENT";

static int temp = 0;
static char *name;
static char *text;
static char *wind_class;
static int rh = 0;

static uint8_t base_r = 150;
static uint8_t base_g = 150;
static uint8_t base_b = 150;

static int led_mode = 0;
static int direction = 1;
static int brightness = 0;

SemaphoreHandle_t data_ready;
SemaphoreHandle_t json_done;

esp_err_t _http_event_handler(esp_http_client_event_t *evt)
{
    static char *output_buffer;  // Buffer to store response of http request from event handler
    static int output_len;       // Stores number of bytes read
    switch(evt->event_id) {
        case HTTP_EVENT_ERROR:
            ESP_LOGD(TAG, "HTTP_EVENT_ERROR");
            break;
        case HTTP_EVENT_ON_CONNECTED:
            ESP_LOGD(TAG, "HTTP_EVENT_ON_CONNECTED");
            break;
        case HTTP_EVENT_HEADER_SENT:
            ESP_LOGD(TAG, "HTTP_EVENT_HEADER_SENT");
            break;
        case HTTP_EVENT_ON_HEADER:
            ESP_LOGD(TAG, "HTTP_EVENT_ON_HEADER, key=%s, value=%s", evt->header_key, evt->header_value);
            break;
        case HTTP_EVENT_ON_DATA:
            ESP_LOGD(TAG, "HTTP_EVENT_ON_DATA, len=%d", evt->data_len);
            /*
             *  Check for chunked encoding is added as the URL for chunked encoding used in this example returns binary data.
             *  However, event handler can also be used in case chunked encoding is used.
             */
            if (!esp_http_client_is_chunked_response(evt->client)) {
                // If user_data buffer is configured, copy the response into the buffer
                int copy_len = 0;
                if (evt->user_data) {
                    copy_len = MIN(evt->data_len, (MAX_HTTP_OUTPUT_BUFFER - output_len));
                    if (copy_len) {
                        memcpy(evt->user_data + output_len, evt->data, copy_len);
                    }
                } else {
                    const int buffer_len = esp_http_client_get_content_length(evt->client);
                    if (output_buffer == NULL) {
                        output_buffer = (char *) malloc(buffer_len);
                        output_len = 0;
                        if (output_buffer == NULL) {
                            ESP_LOGE(TAG, "Failed to allocate memory for output buffer");
                            return ESP_FAIL;
                        }
                    }
                    copy_len = MIN(evt->data_len, (buffer_len - output_len));
                    if (copy_len) {
                        memcpy(output_buffer + output_len, evt->data, copy_len);
                    }
                }
                output_len += copy_len;
            }

            break;
        case HTTP_EVENT_ON_FINISH:
            ESP_LOGD(TAG, "HTTP_EVENT_ON_FINISH");
            if (output_buffer != NULL) {
                // Response is accumulated in output_buffer. Uncomment the below line to print the accumulated response
                // ESP_LOG_BUFFER_HEX(TAG, output_buffer, output_len);
                free(output_buffer);
                output_buffer = NULL;
            }
            output_len = 0;
            break;
        case HTTP_EVENT_DISCONNECTED:
            ESP_LOGI(TAG, "HTTP_EVENT_DISCONNECTED");
            int mbedtls_err = 0;
            esp_err_t err = esp_tls_get_and_clear_last_error((esp_tls_error_handle_t)evt->data, &mbedtls_err, NULL);
            if (err != 0) {
                ESP_LOGI(TAG, "Last esp error code: 0x%x", err);
                ESP_LOGI(TAG, "Last mbedtls failure: 0x%x", mbedtls_err);
            }
            if (output_buffer != NULL) {
                free(output_buffer);
                output_buffer = NULL;
            }
            output_len = 0;
            break;
        case HTTP_EVENT_REDIRECT:
            ESP_LOGD(TAG, "HTTP_EVENT_REDIRECT");
            esp_http_client_set_header(evt->client, "From", "user@example.com");
            esp_http_client_set_header(evt->client, "Accept", "text/html");
            esp_http_client_set_redirection(evt->client);
            break;
    }
    return ESP_OK;
}

static void http_rest_with_url(void)
{
    char local_response_buffer[MAX_HTTP_OUTPUT_BUFFER] = {0};
    /**
     * NOTE: All the configuration parameters for http_client must be spefied either in URL or as host and path parameters.
     * If host and path parameters are not set, query parameter will be ignored. In such cases,
     * query parameter should be specified in URL.
     * 
     * If URL as well as host and path parameters are specified, values of host and path will be considered.
     */
    esp_http_client_config_t config = {
        .url = "http://api.map.baidu.com/weather/v1/?district_id=441900&data_type=all&ak=dR5lADbMe7k7vfbqccF7A9H9iAakgRMz", 
        .event_handler = _http_event_handler,
        .user_data = local_response_buffer,         // Pass address of local buffer to get response
        .disable_auto_redirect = true,
    };
    esp_http_client_handle_t client = esp_http_client_init(&config);

    // GET
    esp_err_t err = esp_http_client_perform(client);
    if (err == ESP_OK) {
        ESP_LOGI(TAG, "HTTP GET Status = %d, content_length = %"PRIu64, 
                    esp_http_client_get_status_code(client),
                    esp_http_client_get_content_length(client));
    } else {
        ESP_LOGE(TAG, "HTTP GET request failed: %s", esp_err_to_name(err));
    }
    // ESP_LOG_BUFFER_HEX(TAG, local_response_buffer, strlen(local_response_buffer));

    cJSON *root = cJSON_Parse(local_response_buffer);
    cJSON *result = cJSON_GetObjectItem(root, "result");
    cJSON *location = cJSON_GetObjectItem(result, "location");
    cJSON *now = cJSON_GetObjectItem(result, "now");

    name = cJSON_GetObjectItem(location, "name")->valuestring;
    text = cJSON_GetObjectItem(now, "text")->valuestring;
    temp = cJSON_GetObjectItem(now, "temp")->valueint;
    rh = cJSON_GetObjectItem(now, "rh")->valueint;
    wind_class = cJSON_GetObjectItem(now, "wind_class")->valuestring;

    ESP_LOGI(TAG, "地区：%s", name);
    ESP_LOGI(TAG, "天气：%s", text);
    ESP_LOGI(TAG, "温度：%d", temp);
    ESP_LOGI(TAG, "湿度：%d", rh);
    ESP_LOGI(TAG, "风力：%s", wind_class);

    xSemaphoreGive(data_ready);

    while (xSemaphoreTake(json_done, pdMS_TO_TICKS(10)) != pdTRUE) {
        vTaskDelay(pdMS_TO_TICKS(100));
    }

    cJSON_Delete(root);

    esp_http_client_cleanup(client);
}

static void http_test_task(void *pvParameters)
{
    while (1) {
        if(gpio_get_level(GPIO_INPUT_PIN) == 0) {
            http_rest_with_url();
            vTaskDelay(pdMS_TO_TICKS(100));
        }
        vTaskDelay(pdMS_TO_TICKS(100));
    }
    
    vTaskDelete(NULL);
}

led_strip_handle_t configure_led(void)
{
    // LED strip general initialization, according to your led board design
    led_strip_config_t strip_config = {
        .strip_gpio_num = BLINK_GPIO,
        .max_leds = LED_STRIP_LED_NUMBERS, 
        .led_pixel_format = LED_PIXEL_FORMAT_GRB, 
        .led_model = LED_MODEL_WS2812, 
        .flags.invert_out = false,
    };

    // LED strip backend configuration: RMT
    led_strip_rmt_config_t rmt_config = {
        .clk_src = RMT_CLK_SRC_DEFAULT,
        .resolution_hz = LED_STRIP_RMT_RES_HZ,
        .flags.with_dma = false,
    };

    // LED strip object handle 
    led_strip_handle_t led_strip;
    ESP_ERROR_CHECK(led_strip_new_rmt_device(&strip_config, &rmt_config, &led_strip));
    ESP_LOGI(TAG, "Create LED strip object with RMT backend");
    return led_strip;
}

static void led_breath(led_strip_handle_t led_strip)
{
    uint8_t color_r = base_r * brightness / 120;
    uint8_t color_g = base_g * brightness / 120;
    uint8_t color_b = base_b * brightness / 120;

    for (int i=0; i<LED_STRIP_LED_NUMBERS; i++) {
        ESP_ERROR_CHECK(led_strip_set_pixel(led_strip, i, color_r, color_g, color_b));
    }
    /* refresh the strip to send data */
    ESP_ERROR_CHECK(led_strip_refresh(led_strip));

    brightness += direction;

    if (brightness == 120 || brightness == 0) {
        direction = -direction;
    }
}

static void led_flow(led_strip_handle_t led_strip)
{
    int tail_lenght = 3;
    
    for(int i=0; i<LED_STRIP_LED_NUMBERS; i++) {
        ESP_ERROR_CHECK(led_strip_clear(led_strip));
        for (int j=0; j<tail_lenght; j++) {
            int position = i - j;
            if (position >= 0) {
                uint8_t tail_r = base_r - (j*(base_r / tail_lenght));
                uint8_t tail_g = base_g - (j*(base_g / tail_lenght));
                uint8_t tail_b = base_b - (j*(base_b / tail_lenght));
                ESP_ERROR_CHECK(led_strip_set_pixel(led_strip, position, tail_r, tail_g, tail_b));
            }
        }
        ESP_ERROR_CHECK(led_strip_set_pixel(led_strip, i, base_r, base_g, base_b));
        ESP_ERROR_CHECK(led_strip_refresh(led_strip));
        vTaskDelay(pdMS_TO_TICKS(100));
    }
}

static void led_task(void *pvParameters)
{
    led_strip_handle_t led_strip = configure_led();
    
    while (1) {
        if(xSemaphoreTake(data_ready, pdMS_TO_TICKS(10))) {
            if(strstr(text, "晴") != NULL) {
                led_mode = 0;
                base_r = 255;
                base_g = 153;
                base_b = 18;
            } else if (strstr(text, "雨") != NULL) {
                led_mode = 1;
                base_r = 255;
                base_g = 18;
                base_b = 18;
            }
            xSemaphoreGive(json_done);
        }
        if (led_mode == 0) {
            led_breath(led_strip);
        } else if (led_mode == 1) {
            led_flow(led_strip);
        }
    }
    vTaskDelete(NULL);
}


void app_main(void)
{
    esp_err_t ret = nvs_flash_init();
    if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
        ESP_ERROR_CHECK(nvs_flash_erase());
        ret = nvs_flash_init();
    }
    ESP_ERROR_CHECK(ret);

    ESP_ERROR_CHECK(esp_netif_init());
    ESP_ERROR_CHECK(esp_event_loop_create_default());

    /* This helper function configures Wi-Fi or Ethernet, as selected in menuconfig.
     * Read "Establishing Wi-Fi or Ethernet Connection" section in
     * examples/protocols/README.md for more information about this function.
     */
    ESP_ERROR_CHECK(example_connect());
    ESP_LOGI(TAG, "Connected to AP, begin http example");


    data_ready = xSemaphoreCreateBinary();
    json_done = xSemaphoreCreateBinary();

    xTaskCreate(&http_test_task, "http_test_task", 8192, NULL, 5, NULL);
    xTaskCreate(&led_task, "led_task", 8192, NULL, 5, NULL);
}
